package atg.applauncher.core.util;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.zip.Adler32;
import java.util.zip.CheckedInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;

public class JarUtils {
	public static String CLASS_VERSION = "123";
	static int BYTE_ARRAY_SIZE = 4096;

	public static void main(String[] pArgs) {
		try {
			createJarFile(new File(pArgs[0]), new File(pArgs[1]), true);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public static void createJarFile(File pFile, File pSourceDir,
			boolean pVerbose) throws IOException {
		JarOutputStream jar = null;
		boolean success = false;
		try {
			jar = new JarOutputStream(new FileOutputStream(pFile));
			File[] toBeProcessed = deepListFiles(pSourceDir);
			if (pVerbose)
				System.out
						.println("Adding " + toBeProcessed.length + " files.");
			for (int i = 0; i < toBeProcessed.length; i++) {
				String fixedRelativePath = getRelativeFileName(
						toBeProcessed[i], pSourceDir).replace(
						File.separatorChar, (char) '/');

				JarEntryInfo info = new JarEntryInfo(toBeProcessed[i],
						fixedRelativePath);

				processEntry(jar, pVerbose, info);
			}
			if (pVerbose)
				System.out.println("Created " + pFile.getAbsolutePath());
			success = true;
		} finally {
			if (jar != null) {
				jar.close();
			}
		}
	}

	public static void createJarFile(File pTargetJarFile, File[] pEntries,
			File pSourceDir, boolean pVerbose) throws IOException {
		JarEntryInfo[] infos = new JarEntryInfo[pEntries.length];
		for (int i = 0; i < pEntries.length; i++) {
			infos[i] = new JarEntryInfo(pEntries[i], getRelativeFileName(
					pEntries[i], pSourceDir).replace(File.separatorChar, '/'));
		}

		createJarFile(pTargetJarFile, infos, pVerbose);
	}

	public static void createJarFile(File pTargetJarFile,
			JarEntryInfo[] pEntries, boolean pVerbose) throws IOException {
		if (pEntries == null)
			return;
		JarOutputStream jos = null;
		boolean success = false;
		try {
			jos = new JarOutputStream(new FileOutputStream(pTargetJarFile));
			for (int i = 0; i < pEntries.length; i++) {
				processEntry(jos, pVerbose, pEntries[i]);
			}
			success = true;
		} finally {
			if (success) {
				jos.close();
			}
		}
	}

	protected static void processEntry(JarOutputStream pJar, boolean pVerbose,
			JarEntryInfo pEntryInfo) throws IOException {
		File pFile = pEntryInfo.getFile();
		if (pFile.isDirectory())
			return;
		FileInputStream file = new FileInputStream(pFile);
		try {
			if (pVerbose)
				System.out.println("Adding " + pEntryInfo.getRelativePath());
			JarEntry entry = new JarEntry(pEntryInfo.getRelativePath());
			pJar.putNextEntry(entry);

			byte[] buffer = new byte[BYTE_ARRAY_SIZE];
			int bytesRead;
			while ((bytesRead = file.read(buffer)) != -1) {
				pJar.write(buffer, 0, bytesRead);
			}
		} finally {
			file.close();
		}
	}

	public static String getRelativeFileName(File pFile, File pRelativeTo) {
		String sourceDir = pRelativeTo.getAbsolutePath();
		String fullPath = pFile.getAbsolutePath();
		if (fullPath.startsWith(sourceDir)) {
			return fullPath.substring(sourceDir.length() + 1);
		}
		return fullPath;
	}

	public static File[] deepListFiles(File pSourceDir) {
		return deepListFiles(pSourceDir, null);
	}

	public static File[] deepListFiles(File pSourceDir, FileFilter pFilter) {
		if (!pSourceDir.exists())
			return null;
		if (!pSourceDir.isDirectory()) {
			File[] f = { pSourceDir };
			return f;
		}
		ArrayList allFiles = new ArrayList();
		processSubDirs(allFiles, pSourceDir, pFilter);
		return (File[]) allFiles.toArray(new File[0]);
	}

	private static void processSubDirs(ArrayList pFileList, File pCurrentDir,
			FileFilter pFilter) {
		if (!pCurrentDir.isDirectory()) {
			pFileList.add(pCurrentDir);
			return;
		}
		File[] files = null;
		if (pFilter == null) {
			files = pCurrentDir.listFiles();
		} else
			files = pCurrentDir.listFiles(pFilter);
		if (files == null)
			return;
		for (int i = 0; i < files.length; i++) {
			if (files[i].isDirectory()) {
				processSubDirs(pFileList, files[i], pFilter);
			} else {
				pFileList.add(files[i]);
			}
		}
	}

	public static void copyDirContentsMaybeUnjar(File pFrom, File pTo,
			boolean pOverwrite) throws IOException {
		copyDirContentsMaybeUnjar(pFrom, pTo, pOverwrite, false, null);
	}

	public static void copyDirContentsMaybeUnjar(File pFrom, File pTo,
			boolean pOverwrite, boolean pVerbose) throws IOException {
		copyDirContentsMaybeUnjar(pFrom, pTo, pOverwrite, pVerbose, null);
	}

	public static void copyDirContentsMaybeUnjar(File pFrom, File pTo,
			boolean pOverwrite, boolean pVerbose, FileFilter[] pFilter)
			throws IOException {
		if (pTo == null)
			return;
		if (pFrom == null)
			return;
		File oldTo = null;
		File tmpTo = null;
		try {
			if ((pTo.exists()) && (!pTo.isDirectory())) {
				File tmpFile = File.createTempFile(
						String.valueOf(System.currentTimeMillis()), "tmp");

				File tmpDir = tmpFile.getParentFile();
				tmpFile.delete();
				tmpTo = new File(tmpDir, "atg-work-" + pTo.getName());
				tmpTo.mkdirs();

				unJar(pTo, tmpTo, false);

				oldTo = pTo;
				pTo = tmpTo;
			}

			if (pFrom.isDirectory()) {
				copyDirContents(pFrom, pTo, pOverwrite, pVerbose, pFilter);
			} else {
				unJar(pFrom, pTo, false);
			}
		} finally {
			if (tmpTo != null) {
				if (oldTo != null) {
					oldTo.delete();

					createJarFile(new File(oldTo.getAbsolutePath()), tmpTo,
							pVerbose);
				}

				deleteDir(tmpTo.getAbsolutePath());
			}
		}
	}

	public static void updateJar(File pJar, String pFileName, File pWorkingDir)
			throws IOException {
		File jarFile = pJar;
		File tempJarFile = new File(pJar.getPath() + ".tmp");

		JarFile jar = new JarFile(jarFile);

		boolean jarUpdated = false;

		try {
			Manifest jarManifest = jar.getManifest();
			JarOutputStream tempJar = new JarOutputStream(new FileOutputStream(
					tempJarFile));

			byte[] buffer = new byte['Ѐ'];

			try {
				FileInputStream file = new FileInputStream(new File(
						pWorkingDir, pFileName));
				int bytesRead;
				try {
					JarEntry entry = new JarEntry(pFileName);
					tempJar.putNextEntry(entry);

					while ((bytesRead = file.read(buffer)) != -1) {
						tempJar.write(buffer, 0, bytesRead);
					}
				} finally {
				}

				for (Enumeration entries = jar.entries(); entries
						.hasMoreElements();) {
					JarEntry entry = (JarEntry) entries.nextElement();
					long modifiedTime = entry.getTime();

					if (!entry.getName().equals(pFileName)) {
						InputStream entryStream = jar.getInputStream(entry);

						JarEntry newEntry = new JarEntry(entry.getName());

						newEntry.setTime(modifiedTime);
						tempJar.putNextEntry(newEntry);
						while ((bytesRead = entryStream.read(buffer)) != -1) {
							tempJar.write(buffer, 0, bytesRead);
						}
					}
				}
				jarUpdated = true;
			} catch (Exception ex) {
				System.out.println(ex);

				tempJar.putNextEntry(new JarEntry("stub"));
				ex.printStackTrace();
			} finally {
				tempJar.close();
			}
		} finally {
			jar.close();

			if (!jarUpdated) {
				tempJarFile.delete();
			}
		}

		if (jarUpdated) {
			jarFile.delete();
			tempJarFile.renameTo(jarFile);
		}
	}

	public static void deleteDir(String iDeleteMe) throws IOException {
		File deleteMe = new File(iDeleteMe);
		if (deleteMe == null)
			return;
		if (deleteMe.isDirectory()) {
			String[] filenames = deleteMe.list();
			for (int ii = 0; ii < filenames.length; ii++) {
				String name = filenames[ii];
				String fullname = iDeleteMe + File.separatorChar + name;
				File f = new File(fullname);
				if (f.isFile()) {
					deleteFile(fullname);
				} else if (f.isDirectory()) {
					deleteDir(fullname);
				}
			}
		}

		deleteMe.delete();
	}

	public static final void deleteFile(String iDeleteMe) {
		File f = new File(iDeleteMe);
		f.delete();
	}

	public static void copyDirContents(File pFrom, File pTo,
			boolean pOverwrite, boolean pLogging) throws IOException {
		copyDirContents(pFrom, pTo, pOverwrite, pLogging, null);
	}

	public static void copyDirContents(File pFrom, File pTo,
			boolean pOverwrite, boolean pLogging, FileFilter[] pFilter)
			throws IOException {
		if ((pFrom == null) || (pTo == null))
			return;
		if (!pFrom.isDirectory()) {
			return;
		}
		pTo.mkdirs();
		File[] files = pFrom.listFiles();
		if (files == null) {
			System.out.println("file " + pFrom + " returns null !!");
		}
		for (int i = 0; i < files.length; i++) {
			File dest = new File(pTo, files[i].getName());
			if (files[i].isFile()) {
				copy(files[i], dest, pOverwrite, pLogging, pFilter);
			} else if (files[i].isDirectory()) {
				copyDirContents(files[i], dest, pOverwrite, pLogging, pFilter);
			}
		}
	}

	public static void copyDirContentsOmitFilesEndingWith(File pFrom, File pTo,
			boolean pOverwrite, boolean pLogging, String pOmitFilesEndingWith)
			throws IOException {
		if ((pFrom == null) || (pTo == null))
			return;
		if (!pFrom.isDirectory()) {
			return;
		}
		pTo.mkdirs();
		File[] files = pFrom.listFiles();
		for (int i = 0; i < files.length; i++) {
			File dest = new File(pTo, files[i].getName());
			if (files[i].isFile()) {
				if (!files[i].getName().endsWith(pOmitFilesEndingWith)) {
					copy(files[i], dest, pOverwrite, pLogging);
				}
			} else if (files[i].isDirectory()) {
				copyDirContents(files[i], dest, pOverwrite, pLogging);
			}
		}
	}

	public static void copyDirContentsNoClobber(File pFrom, File pTo)
			throws IOException {
		if ((pFrom == null) || (pTo == null))
			return;
		if (!pFrom.isDirectory()) {
			return;
		}
		pTo.mkdirs();
		File[] files = pFrom.listFiles();
		for (int i = 0; i < files.length; i++) {
			File dest = new File(pTo, files[i].getName());
			if (files[i].isFile()) {
				copyNoClobber(files[i], dest);
			} else if (files[i].isDirectory()) {
				copyDirContentsNoClobber(files[i], dest);
			}
		}
	}

	public static boolean copy(File pFrom, File pTo, boolean pOverwrite,
			boolean pLog) throws IOException {
		return copy(pFrom, pTo, pOverwrite, pLog, null);
	}

	public static boolean copy(File pFrom, File pTo, boolean pOverwrite,
			boolean pLog, FileFilter[] pFilter) throws IOException {
		boolean copied = false;

		if (pFilter != null) {
			for (int i = 0; i < pFilter.length; i++) {
				if (!pFilter[i].accept(pFrom)) {
					if (pLog)
						System.out.println("Skipping " + pFrom);
					return false;
				}
			}
		}
		if ((!pTo.exists()) || (pFrom.lastModified() > pTo.lastModified())
				|| (pOverwrite)) {
			if (pLog)
				System.out.println("Copying src: " + pFrom + "\n dest: " + pTo);
			copyFile(pFrom, pTo);
			copied = true;

			pTo.setLastModified(pFrom.lastModified());
		}
		return copied;
	}

	public static boolean copyNoClobber(File pFrom, File pTo)
			throws IOException {
		boolean copied = false;
		if (!pTo.exists()) {
			copyFile(pFrom, pTo);
			copied = true;
		}

		pTo.setLastModified(pFrom.lastModified());
		return copied;
	}

	public static void copyFile(File pFrom, File pTo) throws IOException {
		FileChannel srcChannel = new FileInputStream(pFrom).getChannel();
		FileChannel dstChannel = new FileOutputStream(pTo).getChannel();

		dstChannel.transferFrom(srcChannel, 0L, srcChannel.size());
		dstChannel.close();
		srcChannel.close();
	}

	public static void extractEntry(String pEntryPath, File pJarFile,
			File pOutputFile) throws IOException {
		InputStream is = null;
		OutputStream to = null;
		BufferedInputStream from = null;
		try {
			is = getInputStreamForSubFileMaybeZip(pJarFile, pEntryPath);
			if (is == null) {
				throw new IOException("Entry " + pEntryPath + " not found in "
						+ pJarFile);
			}
			from = new BufferedInputStream(is);
			if (!pOutputFile.canWrite()) {
				throw new IOException("File " + pOutputFile.getName()
						+ "is not writable.");
			}
			to = new BufferedOutputStream(new FileOutputStream(pOutputFile));
			byte[] buf = new byte[BYTE_ARRAY_SIZE];
			int read = 0;
			while ((read = from.read(buf)) != -1) {
				to.write(buf, 0, read);
			}
		} finally {
			if (is != null) {
				is.close();
			}
			if (to != null)
				to.close();
			if (from != null)
				from.close();
		}
	}

	public static Map mZipFiles = new HashMap();

	public static boolean hasEntry(String pEntryPath, File pJarFile) {
		ZipEntry zentry = null;
		ZipFile zippy = null;
		try {
			zippy = getZipFile(pJarFile);
			zentry = zippy.getEntry(pEntryPath);
		} catch (IOException e) {
		}

		return zentry != null;
	}

	public static ZipFile getZipFile(File pJarFile) throws ZipException,
			IOException {
		ZipFile zippy = null;

		if (zippy == null) {
			zippy = new ZipFile(pJarFile);
		}

		return zippy;
	}

	public static void closeAllZipFiles() {
		Iterator iter = mZipFiles.values().iterator();
		while (iter.hasNext()) {
			try {
				ZipFile zfile = (ZipFile) iter.next();
				zfile.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	public static void unJar(File pFile, File pTargetDir, boolean pDeleteJar)
			throws IOException {
		ZipInputStream in = null;
		try {
			CheckedInputStream csum = new CheckedInputStream(
					new FileInputStream(pFile), new Adler32());

			in = new ZipInputStream(new BufferedInputStream(csum));

			pTargetDir.mkdir();
			byte[] buff = new byte[BYTE_ARRAY_SIZE];
			ZipEntry ze;
			while ((ze = in.getNextEntry()) != null) {
				if (ze.isDirectory()) {
					new File(pTargetDir, ze.getName()).mkdir();
				} else {
					File out = new File(pTargetDir, ze.getName());

					out.getParentFile().mkdirs();
					BufferedOutputStream ostream = new BufferedOutputStream(
							new FileOutputStream(out));

					int count;
					while ((count = in.read(buff)) >= 0) {
						ostream.write(buff, 0, count);
					}
					ostream.close();

					out.setLastModified(ze.getTime());

					if (out.getName().endsWith(".war")) {
						String dirName = out.getName().substring(0,
								out.getName().indexOf('.'));

						unJar(out, new File(dirName), true);
					}
				}
			}
			if ((pDeleteJar) && (pFile.exists())) {
				pFile.delete();
			}
		} finally {
			if (in != null) {
				in.close();
			}
		}
	}

	public static InputStream getInputStreamForSubFileMaybeZip(File pBaseFile,
			String pRelativePath) throws IOException {
		if (!pBaseFile.exists()) {
			return null;
		}
		if (pBaseFile.isDirectory()) {
			File actualFile = new File(pBaseFile, pRelativePath);
			if (actualFile.exists()) {
				return new FileInputStream(actualFile);
			}
		} else {
			ZipFile zippy = getZipFile(pBaseFile);
			String fixedRelativePath = pRelativePath.replace(
					File.separatorChar, (char) '/');
			ZipEntry zentry = zippy.getEntry(fixedRelativePath);
			if (zentry != null) {
				return zippy.getInputStream(zentry);
			}
		}

		return null;
	}
}